package com.agmte.api.common.crud.core;

import com.agmte.api.common.crud.annotation.NULL;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.NotWritablePropertyException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.beans.PropertyDescriptor;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.*;

@Slf4j
public class PageBeanPropertyRowMapper<T> extends BeanPropertyRowMapper<T> {

    @Nullable
    private Map<String, PropertyDescriptor> mappedFields;


    private boolean primitivesDefaultedForNullValue = false;

    @Nullable
    private Set<String> mappedProperties;

    public PageBeanPropertyRowMapper(Class<T> mappedClass) {
        super(mappedClass);
        this.mappedFields = new HashMap<String, PropertyDescriptor>();
        this.mappedProperties = new HashSet<>();
        for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(mappedClass)) {
            if (pd.getWriteMethod() != null) {
                String lowerCaseName = lowerCaseName(pd.getName());
                mappedFields.put(lowerCaseName, pd);
                String underscoreName = underscoreName(pd.getName());
                if (!lowerCaseName.equals(underscoreName)) {
                    mappedFields.put(underscoreName, pd);
                }
                mappedProperties.add(pd.getName());
            }
        }
    }

    /**
     * 该方法是把 Java 属性名转换成列名，BeanPropertyRowMapper 用来做反向匹配。
     * 你可以根据需要覆盖这个方法，保证驼峰和下划线之间转换正确。
     */
    @Override
    protected String underscoreName(String name) {
        // 例如 "userName" -> "user_name"
        StringBuilder result = new StringBuilder();
        if (name != null && name.length() > 0) {
            result.append(Character.toLowerCase(name.charAt(0)));
            for (int i = 1; i < name.length(); i++) {
                char ch = name.charAt(i);
                if (Character.isUpperCase(ch)) {
                    result.append('_').append(Character.toLowerCase(ch));
                } else {
                    result.append(ch);
                }
            }
        }
        return result.toString();
    }

    @Override
    public T mapRow(ResultSet rs, int rowNumber) throws SQLException {
        BeanWrapperImpl bw = new BeanWrapperImpl();
        initBeanWrapper(bw);

        T mappedObject = constructMappedInstance(rs, bw);
        bw.setBeanInstance(mappedObject);

        ResultSetMetaData rsmd = rs.getMetaData();
        int columnCount = rsmd.getColumnCount();
        Set<String> populatedProperties = (isCheckFullyPopulated() ? new HashSet<>() : null);

        for (int index = 1; index <= columnCount; index++) {
            String column = JdbcUtils.lookupColumnName(rsmd, index);
            String field = lowerCaseName(StringUtils.delete(column, " "));
            PropertyDescriptor pd = (this.mappedFields != null ? this.mappedFields.get(field) : null);
            if (pd != null) {
                try {
                    Object value = getColumnValue(rs, index, pd);
                    if (rowNumber == 0 && logger.isDebugEnabled()) {
                        logger.debug("Mapping column '" + column + "' to property '" + pd.getName() +
                                "' of type '" + ClassUtils.getQualifiedName(pd.getPropertyType()) + "'");
                    }
                    try {
                        value = TypeConversion.convertToEntity(mappedObject.getClass(), pd.getName(), value);
                        bw.setPropertyValue(pd.getName(), value);
                    } catch (TypeMismatchException ex) {
                        if (value == null && this.primitivesDefaultedForNullValue) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("Intercepted TypeMismatchException for row " + rowNumber +
                                        " and column '" + column + "' with null value when setting property '" +
                                        pd.getName() + "' of type '" +
                                        ClassUtils.getQualifiedName(pd.getPropertyType()) +
                                        "' on object: " + mappedObject, ex);
                            }
                        } else {
                            throw ex;
                        }
                    }
                    if (populatedProperties != null) {
                        populatedProperties.add(pd.getName());
                    }
                } catch (NotWritablePropertyException ex) {
                    throw new DataRetrievalFailureException(
                            "Unable to map column '" + column + "' to property '" + pd.getName() + "'", ex);
                }
            } else {
                // No PropertyDescriptor found
                if (rowNumber == 0 && logger.isDebugEnabled()) {
                    logger.debug("No property found for column '" + column + "' mapped to field '" + field + "'");
                }
            }
        }
        if (populatedProperties != null && !populatedProperties.equals(this.mappedProperties)) {
            throw new InvalidDataAccessApiUsageException("Given ResultSet does not contain all fields " +
                    "necessary to populate object of " + this.getMappedClass() + ": " + this.mappedProperties);
        }

        return mappedObject;
    }

    private Object handlerValue(Class<?> entity, String fieldName, Object value) {
//        FieldInfo f = TableHelper.getField(entity, fieldName);
//        if (f != null && f.getTypeHandler() != null
//                && !NULL.class.equals(f.getTypeHandler())) {
//            try {
//                if (f.getFieldType().equals(List.class)) {
//                    value = JSON.parseArray(value.toString(), f.getTypeHandler());
//                } else {
//                    value = JSON.parseObject(value.toString(), f.getTypeHandler());
//                }
//            } catch (Exception e) {
//                log.error(fieldName + " 解析错误", e);
//                return null;
//            }
//        }
        return value;
    }
}